// ChessXMLParser.h
//
// Created By: Jeremy Butler
// Created On: 07-Apr-2011

#include "ChessXMLParser.h"
#include "CS240Exception.h"
#include "ChessUtils.h"
#include "UnitTest.h"

#include <iostream>
#include <fstream>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <string>
using namespace std;

ChessXMLParser::ChessXMLParser() : pieces(), history(){
  return;
}

ChessXMLParser::ChessXMLParser(const ChessXMLParser & other) :  pieces(other.pieces),
								history(other.history){
  return;
}

ChessXMLParser & ChessXMLParser::operator = (const ChessXMLParser & other){
  if(this != &other){
    pieces = other.pieces;
    history = other.history;
  }
  return *this;
}

ChessXMLParser::~ChessXMLParser(){
  Clear();
  return;
}

const vector<pair<Piece *, Location> > & ChessXMLParser::GetPieces() const{
  return pieces;
}

const deque<Move> & ChessXMLParser::GetHistory() const{
  return history;
}

void ChessXMLParser::Parse(const string & fileName){
  Clear();

  string fileString = LoadFile(fileName);

  ParseXML(fileString);

}

string ChessXMLParser::LoadFile(const string & fileName) const{
  ifstream file(fileName.c_str());
  string fileString;

  if(file.is_open()){
    string temp;
    while( file.good() ){
      getline(file,temp);
      fileString += (temp + "\n");
    }

    file.close();
  }
  else throw FileException("Could not open xml file: " + fileName);

  //cout << fileString << endl;
  return fileString;
}

void ChessXMLParser::ParseXML(const string & xmlText){

  boost::match_results<string::const_iterator> what;
  string::const_iterator start = xmlText.begin();
  string::const_iterator end = xmlText.end();
  boost::regex tagExpr("<\\s*((?i:chessgame))\\s*>");

  if(boost::regex_search(start, end, what, tagExpr)){
    string::const_iterator startGame = what[0].second;
    string::const_iterator endGame;
    boost::regex gameExpr("<\\s*/\\s*(?i:chessgame)\\s*>");
    boost::regex_search(startGame, end, what, gameExpr);
    endGame = what[0].first;

    tagExpr.assign("<\\s*((?i:board|history))\\s*>");
    string name;
    while(boost::regex_search(startGame, endGame, what, tagExpr)){
      name = what[1];

      if(boost::iequals(name,"board")){
	string::const_iterator startBoard = what[0].second;
	string::const_iterator endBoard;
	boost::regex boardExpr("<\\s*/\\s*(?i:board)\\s*>");
	boost::regex_search(startBoard, end, what, boardExpr);
	endBoard = what[0].first;
	ParseBoard(startBoard, endBoard);
      }
      else if(boost::iequals(name,"history")){
	string::const_iterator startHistory= what[0].second;
	string::const_iterator endHistory;
	boost::regex historyExpr("<\\s*/\\s*(?i:history)\\s*>");
	boost::regex_search(startHistory, endHistory, what, historyExpr);
	endHistory = what[0].first;
	ParseHistory(startHistory, endHistory);
      }
      startGame = what[0].second;
    }
  }
  else
    throw FileException("Not a valid Chess save file");
}

void ChessXMLParser::ParseBoard(string::const_iterator start, string::const_iterator end){
  boost::match_results<string::const_iterator> what;
  boost::regex pieceExpr("<\\s*(?i:piece)((?:\\s+\\w+\\s*=\\s*\"\\w*\"){4})\\s*/\\s*>");
  while(boost::regex_search(start, end, what, pieceExpr)){
    try{
      pieces.push_back(ParsePiece(what[1]));
    }catch(IOException e){
      //cout << e.GetMessage() << endl;
    }
    start = what[0].second;
  }
}

void ChessXMLParser::ParseHistory(string::const_iterator start, string::const_iterator end){
  boost::match_results<string::const_iterator> what;
  boost::regex pieceExpr("<\\s*(?i:move)\\s*>");
  while(boost::regex_search(start, end, what, pieceExpr)){
      string::const_iterator startMove = what[0].second;
      string::const_iterator endMove;
      boost::regex moveExpr("<\\s*/\\s*(?i:move)\\s*>");
      boost::regex_search(startMove, end, what, moveExpr);
      endMove = what[0].first;
      history.push_back(ParseMove(startMove, endMove));
      start = what[0].second;
  }
}

Move ChessXMLParser::ParseMove(string::const_iterator start, string::const_iterator end){
  boost::match_results<string::const_iterator> what;
  boost::regex pieceExpr("<\\s*(?i:piece)((?:\\s+\\w+\\s*=\\s*\"\\w*\"){4})\\s*/\\s*>");
  boost::regex_search(start, end, what, pieceExpr);
  pair<Piece *, Location> fromPiece = ParsePiece(what[1]);
  start = what[0].second;
  boost::regex_search(start, end, what, pieceExpr);
  pair<Piece *, Location> toPiece = ParsePiece(what[1]);
  start = what[0].second;
  pair<Piece *, Location> * takenPiece = NULL;
  if(boost::regex_search(start, end, what, pieceExpr))
    takenPiece = &(ParsePiece(what[1]));
  Move move;
  if(takenPiece){
    Move wTaken(*(fromPiece.first), fromPiece.second.GetY(), fromPiece.second.GetX(),
		toPiece.first, toPiece.second.GetY(), toPiece.second.GetX(),
		takenPiece->first);
    move = wTaken;
  }
  else{
    Move woTaken(*(fromPiece.first), fromPiece.second.GetY(), fromPiece.second.GetX(),
		toPiece.first, toPiece.second.GetY(), toPiece.second.GetX(), NULL);
    move = woTaken;
  }
  return move;
}

pair<Piece *,Location> ChessXMLParser::ParsePiece(const string & attributes){

  Location location(-1,-1);
  Color color(NO_COLOR);
  PieceType type(NO_TYPE);

  boost::match_results<string::const_iterator> what;
  string::const_iterator start = attributes.begin();
  string::const_iterator end = attributes.end();
  boost::regex expr("(\\w+)\\s*=\\s*\"(\\w*)\"");

  while(boost::regex_search(start, end, what, expr)){
    string attribute = what[1];
    string value = what[2];
    
    if(boost::iequals(attribute,"color"))
      color = Piece::GetPieceColor(value);
    else if(boost::iequals(attribute,"type"))
      type = Piece::GetPieceType(value);
    else if(boost::iequals(attribute,"column")){
      try{
	location.SetX(boost::lexical_cast<int>(value));
      }catch(bad_cast e){
	cout << e.what() << endl;
      }
    }
    else if(boost::iequals(attribute,"row")){
      try{
	location.SetY(boost::lexical_cast<int>(value));
      }catch(bad_cast e){
	cout << e.what() << endl;
      }
    }
    start = what[0].second;
  }
  
  int col = location.GetX();
  int row = location.GetY();
  
  if(color == NO_COLOR || type == NO_TYPE ||
     row > 7 || row < 0 || col > 7 || col <0 ){
    stringstream error;
    error << "Could not parse the piece correctly: Color: " << color << " Type: " << type
      << " Location: (" << row << "," << col << ")";
    throw IOException(error.str());
  }

  Piece * piece = PieceFactory(color, type);
  //cout << piece->ToString() << " Row: "<< location.GetY()
  //     << " Column: " << location.GetX() << endl;
  pair<Piece *,Location> pieceLocPair(piece,location);
  return pieceLocPair;
}

bool ChessXMLParser::Test(ostream & os){
  bool success = true;

  ChessXMLParser parser;

  parser.Parse("games/fewmoves.xml");

  vector<pair<Piece *, Location> > pieces = parser.GetPieces();
  deque<Move> history = parser.GetHistory();
  
  TEST(!pieces.empty());
  TEST(!history.empty());

  cout << "Board" << endl;
  for(int i=0; i < pieces.size(); i++){
    cout << pieces[i].first->ToString()
	 << " Row: " << pieces[i].second.GetY()
	 << " Column: " << pieces[i].second.GetX() << endl;
  }

  cout << "History" << endl;
  for(int i = 1; !history.empty(); i++){
    cout << "Move # " << i << endl;
    cout << history.back().ToString() << endl;
    history.pop_back();
  }


  return success;
}

void ChessXMLParser::Clear(){
  for(int i = 0; i < pieces.size(); i++){
    delete pieces[i].first;
  }
}